<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      margin: 0;
    }
    .canvas{
      display: block;
      background-color: rgb(96, 125, 139);
    }
  </style>
</head>
<body>
  <canvas class="canvas"></canvas>
  <script>
    const canvas = document.querySelector('.canvas')
    const ctx = canvas.getContext("2d");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    ctx.translate(canvas.width / 2, canvas.height); // 现在起点(0, 0)在画布底部中央
    ctx.scale(1, -1); // y坐标轴翻折上去，等同于数学坐标系了 

    const branches = []  // 存放树干的数组
    let rqaId = null

    class Branch {  // 树枝类
      constructor(x, y, len, thick, angle) {
        this.startX = x // 树枝的起点x坐标
        this.startY = y // 树枝起点y坐标
        this.len = len // 这跟树枝长多长
        this.thick = thick // 树枝粗细
        this.angle = angle // 树枝生长分叉角度  给的初始树干垂直向上
        this.progress = 0; // 树枝生长进度
        this.speed = 0.01 + Math.random() * 0.01 // 树枝生长速度 
        // this.isGrowing = true // 是否继续生长 感觉这个属性没什么用啊
        this.hasChildren = true // 阻止生长完成的树枝在并分叉过的树枝在后面继续分叉
        this.getEnd() // 计算树枝末端(endX, endY)
      }

      getEnd() {
        this.endX = this.startX + Math.cos(this.angle * Math.PI / 180) * this.len
        this.endY = this.startY + Math.sin(this.angle * Math.PI / 180) * this.len
      }

      draw() {  // 绘制树枝，模拟生长
        ctx.save()
        ctx.beginPath()
        ctx.strokeStyle = 'brown'
        ctx.lineWidth = this.thick
        ctx.moveTo(this.startX, this.startY)
        const currentX = this.startX + this.progress * (this.endX - this.startX)
        const currentY = this.startY + this.progress * (this.endY - this.startY)
        ctx.lineTo(currentX, currentY) // 绘制到当前生长进度的位置
        ctx.stroke()
        ctx.restore()
      }

      update() { // 长多少，是否需要分叉生成新枝
        this.progress += this.speed  // 更新生长进度

        if (this.progress >= 1) {  // 如果生长进度满了，就可以判断是否生成新树枝
          this.progress = 1 // 防止进度溢出，导致新枝条起点和旧枝条末端不在同一位置
          if (this.len < 8) {  // 树枝长度不够8,就无需生出新枝
            // 枝头最顶端结花的代码
            ctx.save()
            ctx.beginPath()
            ctx.fillStyle = 'rgb(189, 189, 189)'
            ctx.arc(this.endX, this.endY, 3, 0, 2 * Math.PI)
            ctx.fill()
            ctx.restore()
            return
          } else if (this.hasChildren) {  // 如果之前没有分叉过，就生成两个新的枝条
            this.hasChildren = false // 一个树枝只准分叉一次
            let extra = (Math.random() - 0.5) * 10  // 角度额外偏移，增加随机性
            const leftBranch = new Branch(  // 左侧树枝
              this.endX,
              this.endY,
              this.len * (0.7 + Math.random() * 0.15), // 新的枝条更短
              this.thick * 0.68, // 新的枝条更细
              this.angle - getRandom(10, 30) + extra // 原先基础上向左边随机偏离10~30度 + 额外偏移
            )
            branches.push(leftBranch) // 树枝存入数组中
            const rightBranch = new Branch(  // 右侧树枝
              this.endX,
              this.endY,
              this.len * (0.7 + Math.random() * 0.15), // 新的枝条更短
              this.thick * 0.68, // 新的枝条更细
              this.angle + getRandom(10, 30) + extra // 原先基础上向右边边随机偏离10~30度 + 额外偏移
            )
            branches.push(rightBranch)
          }
        }
      }

    }

    function getRandom(min, max) { // 获取范围内随机数
      return Math.floor( Math.random() * (max - min + 1) + min)
    }

    function animate() {  // 生长动画函数
      ctx.clearRect(-canvas.width/2, 0, canvas.width, canvas.height)
      branches.forEach(branch => {
        branch.update() // 更新生长进度
        branch.draw() // 绘制到生长进度
      })
      requestAnimationFrame(animate) // 一帧后重新调用该动画函数
    }

    const trunk = new Branch(0 , 0, 100, 15, 90) // 生成一个初始树枝 用作树干
    branches.push(trunk) // 树干实例存入数组，等待编译器调用其功能
    animate()
  </script>
</body>
</html>